﻿#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using BitwiseOre.Properties;

namespace BitwiseOre.SharedMemory.Core.Common
{
    internal static class TaskExtentions
    {
        private static readonly string TaskNotCompletedMessage = Resources.TaskExtentions_TaskNotCompletedMessage;

        private const TaskContinuationOptions ThenOptions = TaskContinuationOptions.ExecuteSynchronously |
                                                            TaskContinuationOptions.NotOnCanceled |
                                                            TaskContinuationOptions.AttachedToParent;

        private const TaskContinuationOptions LastResultOptions = TaskContinuationOptions.ExecuteSynchronously |
                                                                  TaskContinuationOptions.AttachedToParent;

        public static Task Then(this Task task, Action continuation)
        {
#if DEBUG
            var stacktrace = new StackTrace(true);
#else
            var stacktrace = (StackTrace)null;
#endif
            var then = task.ContinueWith(
                t =>
                    {
                        ThrowIfFaulted(t, stacktrace);
                        continuation();
                    }, ThenOptions);
            return then;
        }

        public static Task Then<T>(this Task<T> task, Action<T> continuation)
        {
#if DEBUG
            var stacktrace = new StackTrace(true);
#else
            var stacktrace = (StackTrace)null;
#endif
            var then = task.ContinueWith(
                t =>
                    {
                        ThrowIfFaulted(t, stacktrace);
                        continuation(t.Result);
                    }, ThenOptions);
            return then;
        }

        public static Task<T> Then<T>(this Task task, Func<T> continuation)
        {
#if DEBUG
            var stacktrace = new StackTrace(true);
#else
            var stacktrace = (StackTrace)null;
#endif
            var then = task.ContinueWith(
                t =>
                    {
                        ThrowIfFaulted(t, stacktrace);
                        return continuation();
                    }, ThenOptions);
            return then;
        }

        public static Task<TOut> Then<TIn,TOut>(this Task<TIn> task, Func<TIn,TOut> continuation)
        {
#if DEBUG
            var stacktrace = new StackTrace(true);
#else
            var stacktrace = (StackTrace)null;
#endif
            var then = task.ContinueWith(
                t =>
                    {
                        ThrowIfFaulted(t, stacktrace);
                        return continuation(t.Result);
                    }, ThenOptions);
            return then;
        }

        public static Task<T> Finally<T>(this Task<T> task, Action cleanup)
        {
            const TaskContinuationOptions options = TaskContinuationOptions.ExecuteSynchronously |
                                                    TaskContinuationOptions.AttachedToParent;

            var source = new TaskCompletionSource<T>(task.AsyncState);

            task.ContinueWith(
                t =>
                    {
                        cleanup();
                        source.TrySetFromTask(t, () => t.Result);
                    },
                options);

            return source.Task;
        }

        public static Task<int> ToCompletedTask(this int result)
        {
            var source = new TaskCompletionSource<int>(TaskCreationOptions.AttachedToParent);
            source.SetResult(result);
            return source.Task;
        }

        public static Task<T> LastResult<T>(this IEnumerable<Task<T>> tasks)
        {
            var completionsource = new TaskCompletionSource<T>();
            var enumerator = tasks.GetEnumerator();

            if (enumerator.MoveNext())
            {
                enumerator.Current.ContinueWith(
                    t => LastResultContinuation(t, completionsource, enumerator),
                    LastResultOptions).IgnoreExceptions();
            }
            else
            {
                completionsource.SetResult(default(T));
            }

            return completionsource.Task;
        }

        private static void LastResultContinuation<T>(Task<T> task, TaskCompletionSource<T> completionSource,
                                                      IEnumerator<Task<T>> enumerator)
        {
            switch (task.Status)
            {
                case TaskStatus.RanToCompletion:
                    if (enumerator.MoveNext())
                    {
                        enumerator.Current.ContinueWith(
                            t => LastResultContinuation(t, completionSource, enumerator),
                            LastResultOptions).IgnoreExceptions();
                    }
                    else
                    {
                        completionSource.TrySetResult(task.Result);
                    }
                    break;
                case TaskStatus.Canceled:
                    completionSource.TrySetCanceled();
                    break;
                case TaskStatus.Faulted:
                    completionSource.TrySetException(task.Exception.InnerExceptions);
                    break;
                default:
                    throw new InvalidOperationException(TaskNotCompletedMessage);
            }
        }

#pragma warning disable 168 // Variable is never used
        private static void IgnoreExceptions(this Task task)
        {
            task.ContinueWith(t => { var ignored = t.Exception; },
                              TaskContinuationOptions.OnlyOnFaulted | 
                              TaskContinuationOptions.ExecuteSynchronously);
        }
#pragma warning restore 168 // Variable is never used

        private static void ThrowIfFaulted(Task t, StackTrace trace)
        {
            if (t.Exception == null) return;

            var ex = new AggregateException("Then continuation propegated one or more exceptions.",t.Exception);
            if (trace != null) ex.Data["CallContextStackTrace"] = trace;
            throw ex;
        }
    }
}